import sys
import os
import json
import requests
import re
import subprocess
from PyQt6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QFileDialog, QMessageBox, QTextEdit
)
from PyQt6.QtGui import QClipboard
from PyQt6.QtCore import Qt


class BiliDownloader(QWidget):
    def __init__(self):
        super().__init__()
        self.config = self.load_config()  # 加载配置文件
        self.init_ui()
        self.clipboard = QApplication.clipboard()  # 获取系统剪贴板

    def load_config(self):
        """从配置文件中读取配置信息"""
        config_path = r"C:\Users\config.json"  # 配置文件路径
        try:
            with open(config_path, 'r', encoding='utf-8') as config_file:
                config = json.load(config_file)
            return config
        except FileNotFoundError:
            QMessageBox.critical(self, "错误", f"配置文件 {config_path} 未找到！")
            sys.exit(1)
        except json.JSONDecodeError:
            QMessageBox.critical(self, "错误", f"配置文件 {config_path} 格式错误！")
            sys.exit(1)

    def init_ui(self):
        # 设置窗口标题和大小
        self.setWindowTitle("B站视频下载器")
        self.setGeometry(100, 100, 600, 300)

        # 创建布局
        layout = QVBoxLayout()

        # URL 输入框
        url_layout = QHBoxLayout()
        url_label = QLabel("视频 URL:")
        self.url_input = QLineEdit()
        self.paste_button = QPushButton("粘贴")
        self.paste_button.clicked.connect(self.paste_url)
        self.clear_button = QPushButton("清空")
        self.clear_button.clicked.connect(self.clear_url)
        url_layout.addWidget(url_label)
        url_layout.addWidget(self.url_input)
        url_layout.addWidget(self.paste_button)
        url_layout.addWidget(self.clear_button)
        layout.addLayout(url_layout)

        # p 参数输入框
        p_layout = QHBoxLayout()
        p_label = QLabel("P 参数：")
        self.p_input = QLineEdit()
        self.p_input.setPlaceholderText("单个或范围，如 1 或 1-5")  # 设置占位符文本
        p_layout.addWidget(p_label)
        p_layout.addWidget(self.p_input)
        layout.addLayout(p_layout)

        # 保存路径选择
        path_layout = QHBoxLayout()
        path_label = QLabel("保存路径:")
        self.path_input = QLineEdit(self.config.get('output_dir', '.'))
        self.path_input.setReadOnly(True)
        self.browse_button = QPushButton("浏览")
        self.browse_button.clicked.connect(self.select_output_path)
        path_layout.addWidget(path_label)
        path_layout.addWidget(self.path_input)
        path_layout.addWidget(self.browse_button)
        layout.addLayout(path_layout)

        # 下载按钮
        self.download_button = QPushButton("开始下载")
        self.download_button.clicked.connect(self.start_download)
        layout.addWidget(self.download_button)

        # 下载状态显示
        self.status_text = QTextEdit()
        self.status_text.setReadOnly(True)
        self.status_text.setPlaceholderText("下载状态将在这里显示...")
        layout.addWidget(self.status_text)

        # 设置主布局
        self.setLayout(layout)

    def paste_url(self):
        """从剪贴板粘贴 URL"""
        clipboard_text = self.clipboard.text()
        self.url_input.setText(clipboard_text)

    def clear_url(self):
        """清空 URL 输入框"""
        self.url_input.clear()

    def select_output_path(self):
        """选择保存路径"""
        output_dir = QFileDialog.getExistingDirectory(self, "选择保存路径")
        if output_dir:
            self.path_input.setText(output_dir)

    def start_download(self):
        """开始下载"""
        url = self.url_input.text().strip()
        p_param = self.p_input.text().strip()
        output_dir = self.path_input.text().strip()

        if not url:
            QMessageBox.warning(self, "警告", "请输入视频 URL！")
            return
        if not output_dir:
            QMessageBox.warning(self, "警告", "请选择保存路径！")
            return

        # 处理 p 参数
        p_values = self.parse_p_param(p_param)
        if not p_values:
            QMessageBox.warning(self, "警告", "P 参数格式错误！请输入单个数字或范围（如 1 或 1-5）")
            return

        # 遍历所有 p 值，逐个下载
        for p in p_values:
            self.update_status(f"开始下载 P={p} 的视频")
            self.download_video(url, output_dir, p)

    def parse_p_param(self, p_param):
        """解析 p 参数，返回一个数字列表"""
        if not p_param:
            return [1]  # 默认为 1
        if '-' in p_param:
            start, end = map(int, p_param.split('-'))
            return list(range(start, end + 1))
        elif p_param.isdigit():
            return [int(p_param)]
        else:
            return []

    def download_video(self, url, output_dir, p=1):
        """下载视频的逻辑"""
        # 添加 p 参数到 URL
        if p != 1:
            url = f"{url}&p={p}"

        # 从配置文件中获取Cookie
        cookie = self.config.get('cookie')
        if not cookie:
            QMessageBox.critical(self, "错误", "配置文件中未设置 Cookie！")
            return

        # 设置请求头
        headers = {
            "Referer": url,
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
            "Cookie": cookie
        }

        # 发送请求
        try:
            response = requests.get(url=url, headers=headers)
            html = response.text

            # 提取视频标题
            pattern = r'"pages":\s*(\[.*?\]),\s*"subtitle"'
            match = re.search(pattern, html, re.DOTALL)
            pages_json = match.group(1)  # 提取匹配到的 JSON 字符串
            pages_list = json.loads(pages_json)  # 将 JSON 字符串解析为 Python 列表

            title = pages_list[p-1]["part"]
            title = re.sub(r'[\\/*?:"<>|]', '', title)  # 去掉文件名中不允许的字符
            self.update_status(f"视频标题获取完成：{title} ")

            # 提取 folder_title
            folder_title = re.findall('title="(.*?)"', html)[0]
            folder_title = re.sub(r'[\\/*?:"<>|]', '', folder_title)  # 去掉文件夹名中不允许的字符
            self.update_status(f"文件夹标题获取完成：{folder_title}")

            # 创建文件夹
            folder_path = os.path.join(output_dir, folder_title)
            if not os.path.exists(folder_path):
                os.makedirs(folder_path)
                self.update_status(f"文件夹创建完成，路径为：{folder_path}")
            else:
                self.update_status(f"文件夹已存在，路径为：{folder_path}")

            # 提取播放信息
            info = re.findall('window.__playinfo__=(.*?)</script>', html)[0]
            json_data = json.loads(info)

            # 获取视频和音频URL
            video_url = json_data['data']['dash']['video'][0]['baseUrl']
            self.update_status("视频地址获取完成!")

            audio_url = json_data['data']['dash']['audio'][0]['baseUrl']
            self.update_status("音频地址获取完成!")

            # 下载视频和音频内容
            video_content = requests.get(url=video_url, headers=headers).content
            audio_content = requests.get(url=audio_url, headers=headers).content

            # 保存视频和音频文件
            video_save_path = os.path.join(folder_path, f"video.mp4")  # 修改为保存到文件夹内
            audio_save_path = os.path.join(folder_path, f"audio.mp3")  # 修改为保存到文件夹内

            with open(video_save_path, mode='wb') as v:
                v.write(video_content)
            self.update_status(f"视频文件保存完成，路径为：{video_save_path} ")

            with open(audio_save_path, mode='wb') as a:
                a.write(audio_content)
            self.update_status(f"音频文件保存完成，路径为：{audio_save_path}")

            # 合并视频和音频
            output_file = os.path.join(folder_path, f"{title}.mp4")  # 修改为保存到文件夹内
            self.merge_video_audio(video_save_path, audio_save_path, output_file)
            self.update_status(f"视频下载完成，保存路径为：{output_file}")

        except Exception as e:
            self.update_status(f"{title}下载失败：{e}")

    def merge_video_audio(self, video_path, audio_path, output_path):
        """合并视频和音频"""
        command = [
            'ffmpeg',
            '-y',  # 覆盖输出文件
            '-i', video_path,  # 输入视频文件
            '-i', audio_path,  # 输入音频文件
            '-c:v', 'copy',  # 视频复制，不重新编码
            '-c:a', 'aac',  # 音频编码为 AAC
            '-strict', 'experimental',  # 允许非标准选项
            output_path  # 输出文件路径
        ]
        try:
            subprocess.run(command, check=True)
            self.update_status(f"视频和音频合并成功，输出路径为：{output_path}")
        except subprocess.CalledProcessError as e:
            self.update_status(f"合并失败：{e}")

    def update_status(self, message):
        """更新下载状态显示"""
        self.status_text.append(message)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = BiliDownloader()
    window.show()
    sys.exit(app.exec())